// page.c

// PMM code.
//
// handles page directories
//         page stacks
//         mapping pages

#include <master.h>
#include <debug.h>


// page stacks. 1 each for:
//
// * 4m pages - kernel usage
// * 4k pages - applications

static page_t *page_stack_4k,      *page_stack_4m;
static page_t *page_stack_4k_head, *page_stack_4m_head;

// book keeping

static unsigned int pages_4k_avail = 0, pages_4k_mapped = 0;
static unsigned int pages_4m_avail = 0, pages_4m_mapped = 0;

static unsigned int pages_4k_resvd = 0, 
		    pages_4m_resvd = 3; // pd, stacks

// master kernel PD

static page_t *kern_pd;


// handler
void handle_page_fault(struct regs *regs) {
  unsigned int faultaddr = _read_cr2();
  dprint("caught a page fault, addr %08x", faultaddr); // xxx
  
  printf("page fault, addr %08x", faultaddr);

  int do_panic = 1;
 
  if (regs->err_code & PAGE_PRESENT) {
      // page was present
      printf(", present");
  } 
 
  if (regs->err_code & PAGE_RW) {
      // page was RW
      printf(", read/write");
  } else {
      printf(", read only");
      if (regs->err_code & PAGE_USER) {
         // do COW paging
      }
  }

  if (regs->err_code & PAGE_USER) {
      // fault in user mode
      // kill task and move on
      printf(", in user space\n");
  } else {

  switch(faultaddr & 0xf0000000) {
    case 0xc0000000:
	  printf(", in kernel space");
          break;
    case 0xd0000000: // kernel heap
	  printf(", in kernel heap");
	  break;

    case 0xe0000000: // 
	  printf(", in driver area\n");
	  break;

    case 0xf0000000: // 
	  printf(", in mm area\n");
	  break;
    default:
	  printf(", at bogus address\n");
    }
  }
  if (do_panic)  
  panic("page fault");
}

////
// handles initial page stack setup

static void push_page_addr(unsigned int base) {
  dprint("push page %p\n", base); 

  if (MASK_4M_LOW(base) != 0 ) {
    
    panic("trying to push non-aligned page");
  }

  if (base < 0x100000) return; // reserve low mem (1mb)

  if ((base > (unsigned int)&kern_start) && 
      (base < ((unsigned int)&kern_end) + (pages_4m_resvd * PAGE_SIZE_4M))) 
	return; // don't put pages from kernel address space 
		// or reserved pages

  page_stack_4m++;
  *page_stack_4m = MASK_4M(base); 
  
  pages_4m_avail++;
}

////
// 4m page stack

//// XXX move this please!
// when to flush tlb
// 1. unmapping pages
// 2. flags
// 2b. guard pages

page_t *pop_page_4m() {
  page_t * ret = page_stack_4m;
  if (pages_4m_avail == 0) {
    panic("out of 4m pages");
  }
  page_stack_4m--;
  pages_4m_avail--;
  return ret;
}

void push_page_4m(page_t *page) {
  page_stack_4m++;
  *page_stack_4m = *page;
  pages_4m_avail++;
}

page_t *pop_page_4k() {
  page_t * ret = page_stack_4m;
  if (pages_4k_avail == 0) {
    /// XXX should map a 4m page
    panic("out of 4k pages");
  }
  page_stack_4k--;
  pages_4k_avail--;
  return ret;
}

void push_page_4k(page_t *page) {
  page_stack_4k++;
  *page_stack_4k = *page;
  pages_4k_avail++;
}

////
// helper to build page stack

static void add_pages(unsigned int base, unsigned int length) {
  unsigned int i, num_pages = length / PAGE_SIZE_4M;

  if (num_pages == 0) return;
 
  printf("Add %d pages, base %08x, length %08x\n", num_pages, base, length);
  printf("Will need %08x bytes for storage\n", num_pages * 16);

  for (i = 0; i < num_pages; i++) {
    push_page_addr(base + (i * PAGE_SIZE_4M)); 
  }    
}

////
// helper

unsigned int virt2phys(unsigned int what) {
  unsigned int index, r;
  index = MASK_4M(what) / PAGE_SIZE_4M;
  
  r = kern_pd[index];
  printf("translate %08x, index %d\n", r, index);

  if ((r & 0x1) == 0) {
   printf("tried to translate non-present %08x\n", what);
   panic("can't translate an address!");
  }

  if (r & PAGE_4M) {
    r = MASK_4M(kern_pd[index]) | MASK_4M_LOW(what);
  } else {
    panic("4k page ugh");
  }

  return r;
}


///
// mapping and unmapping pages

static inline void map_page_4m_addr(unsigned int phys, unsigned int virt, unsigned int flags) {
  unsigned int page_index;

  page_index = MASK_4M(virt) / PAGE_SIZE_4M;
  kern_pd[page_index] = MASK_4M(phys) | flags;
//  printf("set %d to %08x\n", page_index, kern_pd[page_index]);
}

static inline void unmap_page_4m_addr(unsigned int virt) {
  unsigned int page_index;

  page_index = MASK_4M(virt) / PAGE_SIZE_4M;;
  kern_pd[page_index] = 0;
}

static void pd_set(unsigned int index, page_t what) {
  kern_pd[index] = what;
}

static void pd_unset(unsigned int page_index) {
  kern_pd[page_index] = 0;
}


////
// helpers

void page_4m_to_4k() {
  page_t base, iter;
  base = *pop_page_4m();
  for(iter = 0; iter <= PAGE_SIZE_4M; iter+= PAGE_SIZE_4K) {
   push_page_4k(base+iter);
  }
}

void map_page_4m(page_t what, unsigned int where) {
  map_page_4m_addr((unsigned int)what, where, PAGE_FLAGS_4M);
}

void unmap_page_4m(page_t what) {
  unmap_page_4m_addr((unsigned int)what);
}

////
// 4k page things
//
// handles page dirs and mapping 4k pages in them

void map_pagedir(page_t ptaddr, unsigned int area) {
  pd_set((MASK_4M(area) / PAGE_SIZE_4M), ptaddr);
}

#define unmap_pagedir(x) unmap_page_4m(x)

void map_page_4k(page_t *pagedir, unsigned int virt, unsigned int phys) {
  pagedir[MASK_4K(MASK_4M_LOW(virt)) / PAGE_SIZE_4K] = phys | PAGE_FLAGS_4K;
}

void unmap_page_4k(page_t *pagedir, unsigned int virt) {
  pagedir[MASK_4K(MASK_4M_LOW(virt)) / PAGE_SIZE_4K] = 0;
}


void build_initial_pd() {
  unsigned int kern_num_pages, j, base_addr;
  page_t *old_kern_pd; 

  kern_pd = (unsigned int *)ALIGN_4M((unsigned int)(&kern_end));

  // build new page directory
  for (j = 0; j < 1024; j++) {
    kern_pd[j] = 0; 
  }

  // now, the page dir is divided into 1024 entries, each controlling 
  // 4mb of ram

  // all this hackery is cause the kernel is loaded with a 3gb offset 
  // from phys

  kern_num_pages = (ALIGN_4M(&kern_end) - MASK_4M(&kern_start));
  kern_num_pages /= PAGE_SIZE_4M;
  kern_num_pages += pages_4m_resvd;  // page directory, 4m page stack, 4k stack
  
  base_addr = MASK_4M(&kern_start);

  dprint("Kernel needs %d pages mapped at addr %08x.\n", 
	kern_num_pages, base_addr);
 
  // map dem pages
  for (j = 0; j <= kern_num_pages; j++) {
    map_page_4m(j * PAGE_SIZE_4M, base_addr + (j * PAGE_SIZE_4M)); 
  }
 
  // [16:44] <@sekio> its because the page dir has to be a PHYSICAL ADDRESS
  _write_cr3((unsigned int)kern_pd - 0xc0000000);

  dprint("If you can read this, we haven't munged the PD! whee\n");

  // relocate page dir for the sake of it
  map_page_4m(virt2phys((unsigned int)kern_pd), 0xf0000000);
  old_kern_pd = kern_pd;
  kern_pd = (page_t *)0xf0000000;
  unmap_page_4m((unsigned int)old_kern_pd);

  dprint("Page directory at %08p / %08x\n", kern_pd, virt2phys((unsigned int)kern_pd));
}



void build_page_stack(multiboot_info_t *mbi) {
  unsigned int newpagebase;

  printf("mbi is at %08p", mbi);
  if ((unsigned int)mbi  >= 0x800000) {
    printf(" which is unmapped!\n"); 	 

    // ideally we should map MBI
    panic("MBI unaccessible");
  }
  
  // fixup
  mbi += 0xc0000000;

  printf("\n");


  // set up page stack

  page_stack_4m = page_stack_4m_head = (page_t *)(ALIGN_4M((unsigned int)&kern_end) + PAGE_SIZE_4M);
  page_stack_4k = page_stack_4k_head = (page_stack_4m + PAGE_SIZE_4M);

   
  // initially, no pages
  memset(page_stack_4m, 0, sizeof(page_t));
  //memset(page_stack_4k, 0, sizeof(page_t));

  if (CHECK_FLAG (mbi->flags, 6))
    {
      memory_map_t *mmap;

      dprint("Building page stack.\n");      

      for (mmap = (memory_map_t *) mbi->mmap_addr;
	   (unsigned long) mmap < mbi->mmap_addr + mbi->mmap_length;
	   mmap = (memory_map_t *) ((unsigned long) mmap
				    + mmap->size + sizeof (mmap->size)))
	if (mmap->type == 1 && (mmap->base_addr_high == 0)) {
                if (mmap->length_high != 0) panic("don't want to handle this");
                newpagebase = ALIGN_4M(mmap->base_addr_low);
                dprint("Adding pages, base %p, addr %p\n", newpagebase, mmap->length_low);
       		add_pages(newpagebase, mmap->length_low);	
	}
    }

  printf("%d pages of %d bytes allocated, ", pages_4m_avail, PAGE_SIZE_4M);
  printf("%d mbytes availiable\n", (pages_4m_avail * PAGE_SIZE_4M) / (1024*1024));

  if (pages_4m_avail < 4) panic("Not enough memory!");
}

char * hacky_heap;
extern char * _kmalloc_fuck_you;

void setup_mm(multiboot_info_t *mbi) {
  dprint("Setup MM called\n");
  build_page_stack(mbi);
  dprint("Built page stack\n");
  build_initial_pd();
  dprint("Built PD\n");
  //  build_kernel_heap();

  dprint("Setting up hacky heap at %p\n", &_kmalloc_fuck_you);
  hacky_heap = &_kmalloc_fuck_you;
  
  // todo: lowmem bitmap, kernel heap
}
/// XXXXX
void *kmalloc_stupid(int howmany) {
  char * ret = hacky_heap;
  // it's a heap, no, i moved it
  hacky_heap += howmany;
  dprint("Hacky malloc %p\n", ret);
  return ret; 
}

void kfree(void *x) {
  dprint("Free %p\n",x); 
  // HURR NOTHING
}
